﻿// Copyright (c) Files Community
// Licensed under the MIT License.

namespace Files.Core.SourceGenerator.Generators
{
	[Generator(LanguageNames.CSharp)]
	internal sealed class CommandManagerGenerator : IIncrementalGenerator
	{
		public void Initialize(IncrementalGeneratorInitializationContext context)
		{
			var sources = context.SyntaxProvider.ForAttributeWithMetadataName(
				"Files.Shared.Attributes.GeneratedRichCommandAttribute",
				static (node, token) => true,
				static (context, token) => context)
					.Collect();

			context.RegisterSourceOutput(sources, (context, sources) =>
			{
				if (sources.Length is 0)
					return;

				List<string> commandNames = [];
				foreach (var source in sources)
				{
					var typeSymbol = (INamedTypeSymbol)source.TargetSymbol;
					var commandName = typeSymbol.Name;

					if (typeSymbol.Name.EndsWith("Action"))
						commandName = typeSymbol.Name[..^"Action".Length];

					commandNames.Add(commandName);
				}

				commandNames.Sort();

				string commandCodesEnum = GenerateCommandCodes(commandNames);
				context.AddSource($"CommandCodes.g.cs", commandCodesEnum);

				string commandManagerInterface = GenerateICommandManager(commandNames);
				context.AddSource($"ICommandManager.g.cs", commandManagerInterface);

				string commandManagerClass = GenerateCommandManager(commandNames);
				context.AddSource($"CommandManager.g.cs", commandManagerClass);
			});
		}

		private string GenerateCommandCodes(IEnumerable<string> commandNames)
		{
			StringBuilder builder = new();
			builder.AppendLine($"// <auto-generated/>");
			builder.AppendLine($"");
			builder.AppendLine($"namespace Files.App.Data.Commands;");
			builder.AppendLine($"");
			builder.AppendLine($"public enum CommandCodes");
			builder.AppendLine($"{{");

			builder.AppendLine($"	None,");
			builder.AppendLine($"");

			foreach (var enumName in commandNames)
				builder.AppendLine($"	{enumName},");

			builder.AppendLine($"}}");

			return builder.ToString();
		}

		private string GenerateICommandManager(IEnumerable<string> commandNames)
		{
			StringBuilder builder = new();
			builder.AppendLine($"// <auto-generated/>");
			builder.AppendLine($"");
			builder.AppendLine($"namespace Files.App.Data.Commands;");
			builder.AppendLine($"");
			builder.AppendLine($"public interface ICommandManager : IEnumerable<IRichCommand>");
			builder.AppendLine($"{{");

			builder.AppendLine($"	IRichCommand this[CommandCodes code] {{ get; }}");
			builder.AppendLine($"	IRichCommand this[string code] {{ get; }}");
			builder.AppendLine($"	IRichCommand this[HotKey customHotKey] {{ get; }}");

			builder.AppendLine($"");
			builder.AppendLine($"	IRichCommand None {{ get; }}");
			builder.AppendLine($"");

			foreach (var commandName in commandNames)
				builder.AppendLine($"	IRichCommand {commandName} {{ get; }}");

			builder.AppendLine($"}}");

			return builder.ToString();
		}

		private string GenerateCommandManager(IEnumerable<string> commandNames)
		{
			StringBuilder builder = new();
			builder.AppendLine($"// <auto-generated/>");
			builder.AppendLine($"");
			builder.AppendLine($"using global::Files.App.Actions;");
			builder.AppendLine($"using global::System.Collections.Frozen;");
			builder.AppendLine($"");
			builder.AppendLine($"namespace Files.App.Data.Commands;");
			builder.AppendLine($"");
			builder.AppendLine($"internal sealed partial class CommandManager");
			builder.AppendLine($"{{");

			builder.AppendLine($"	private readonly FrozenDictionary<CommandCodes, IRichCommand> commands;");
			builder.AppendLine($"");

			builder.AppendLine($"	public IRichCommand None => commands[CommandCodes.None];");
			builder.AppendLine($"");

			foreach (var commandName in commandNames)
				builder.AppendLine($"	public IRichCommand {commandName} => commands[CommandCodes.{commandName}];");

			builder.AppendLine($"");
			builder.AppendLine($"	private static Dictionary<CommandCodes, IAction> CreateActions() => new()");
			builder.AppendLine($"	{{");

			foreach (var commandName in commandNames)
				builder.AppendLine($"		[CommandCodes.{commandName}] = new {commandName}Action(),");

			builder.AppendLine($"	}};");

			builder.AppendLine($"}}");

			return builder.ToString();
		}
	}
}
